__Calendars = {}
DAY_S       = 60*60*24
DAY_MS      = DAY_S*1000
MONTH_NAMES = []
DAY_NAMES   = []

if !jQuery?
    alert 'jQuery is required!'
else if ! (jQuery.ui)?
    alert 'jQuery UI is required!'
else
    (->
      $.fn.extend
        calendar: (options)->
            C = new Calendar @, options
            C.init()
            Calendar.set(C)
    ) jQuery

class Calendar
  currentDate:  null
  uuid:         null
  element:      null
  table:        null
  controls:     null
  items:        {}
  itemsByDate:  {}
  itemsByTime:  {}
  tasks:        []
  options:
    view:       'month'
    weekStart:  0
    years:      ['-10','+10']
    timeStep:   30
    monthNames:
        long:  ['January', 'February', 'March', 'April', 'May', 'June', 'July', 
                'August', 'September', 'October', 'November', 'December']
        short: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
    dayNames:
        long:  ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
        short: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
    
    prevText: '&laquo;'
    nextText: '&raquo;'
    textMonth: 'Month'
    textWeek:  'Week'
    textDay:   'Day'
    
    itemClick: ->
    onShow: ->

  constructor: (@element, options)->
    @options = $.extend true, @options, options
    
  init: ->
    @uuid = @generateUUID()
    @element
      .addClass 'calendar-container'
      .data 'calendar', @
        
    MONTH_NAMES = @options.monthNames
    DAY_NAMES   = @options.dayNames
    
    @loadURLParams()
    if @options.date
      @setDate @options.date
    else
      @setDate()
    
    if @options.data
      for i  in [0..@options.data.length]
        @addItem @options.data[i]
    @tasks = @options.tasks
    
    @drawControls()
    @draw true
    
  generateUUID: ->
    uuid = ""
    buffer = "qwertyuiopasdfghjklzxcvbnm1234567890"
    for i in [0..16]
      uuid += buffer.substr(Math.round(Math.random()*(buffer.length-1)), 1)
                    .toUpperCase()
    uuid
  
  addItem: (item)->
    unless (item.id)?
      item.id = @generateUUID()
    @items[item.id] = item
    @refactorItems()

  refactorItems: ->
    item = null
    @itemsByDate = {}
    @itemsByTime = {}
    
    for item in @items
      d = new MyDate(item.start_datetime)
      date = d.format 'Y-m-d'
      time = d.format 'Y-m-dTH-i'
      unless (@itemsByDate[date])?
          @itemsByDate[date] = []
      
      unless (@itemsByTime[time])?
          @itemsByTime[time] = []

      @itemsByDate[date].push item
      @itemsByTime[time].push item
      
  findItem: (id)->
    for item in @items
      if item.id == id
        return item
    
    false
  findItemElement: (id)->
    @table.find '#item-'+id
  
  moveItem: (itemElement, td)->
      c = td.find '.calendar-items-container'
      unless c.length
        c = $('<div class="calendar-items-container">').appendTo td
      
      item = itemElement.data 'item'
      
      newd = new MyDate(td.attr('date').replace(/T(\d+)-(\d+)$/g, ' $1:$2'))
      newDate = newd.format 'Y-m-d'
      newTime = newd.format 'H:i'
      
      c.append itemElement
      
      @items[item.id].start_datetime = newd.format 'Y-m-d H:i'
      @refactorItems()
  
  removeItem: (itemId)->
    item = @items[itemId]
    if item
      if confirm('Do you want to delete "'+item.title+'"?')
          span = $ 'span#item-'+itemId
          d = new MyDate(item.start_datetime)
          date = d.format 'Y-m-d'
          time = d.format 'Y-m-dTH-i'
          for i in [0..@itemsByTime[time]]
            if @itemsByTime[time][i].id == itemId
              @itemsByTime[time].splice i, 1
              break
          for i in [0..@itemsByDate[date]]
            if @itemsByDate[date][i].id == itemId
              @itemsByDate[date].splice i, 1
              break
          delete @items[itemId]
          @refactorItems()
          span.remove()
    
  load: (url)->
    params = @parseURLParams url
    @setDate params.date
    @options.view = params.view
    location.href = @getURLParams()
    @redraw()
  
  loadURLParams: ->
    params = @parseURLParams()
    if params
      @options.date = params.date
      @options.view = params.view
  
  parseURLParams: (url)->
    ret = false;
    url = if url then url else location.href
    pos  = url.indexOf "#!"
    if pos>0
      items =  url.substr(pos+2).split "/"
      ret = {
        date: if items[0] then items[0] else (new MyDate()).format('Y-m-d')
        view: if items[1] then items[1] else 'month'
      }
    ret
  
  setDate: (date)->
    if date
      @currentDate = new MyDate(date)
    else
      @currentDate = new MyDate()
  getDate: (format)->
    if format
      return @currentDate.format format
    else
      return @currentDate
  
  getURLParams: (date, view)->
    ret = "#!";
    ret += (if date then date else @getDate('Y-m-d')) + "/"
    ret += if view then view else @options.view
    ret
  
  getURLParamsDir: (amount, date, view)->
    d = if date then (new MyDate(date)) else @getDate()
    v = if view then view else @options.view
    
    params = {}
    switch @options.view
      when 'month'
        params = @getURLParams(d.addMonth(amount).format('Y-m-d'), v)
          
      when 'week'
        params = @getURLParams(d.addTime(amount*7*DAY_MS).format('Y-m-d'), v)
          
      when 'day'
        params = @getURLParams(d.addTime(amount*DAY_MS).format('Y-m-d'), v)
    params

  getURLParamsPrev: (date, view)->
    @getURLParamsDir -1, date, view
  
  getURLParamsNext: (date, view)->
    @getURLParamsDir 1, date, view
  
  draw: ->
    switch @options.view
      when 'day'
        @drawDay()
      when 'week'
        @drawWeek()
      else
        @drawMonth()

    @onShow()
    @table.find('td').droppable
      accept: '.calendar-item'
      hoverClass: 'drop-hover'
      drop: (e, ui)->
        calendar = $(@).parent().parent().parent().parent().data('calendar')
        if calendar
          calendar.moveItem ui.draggable, $(@)
        else
          console.error 'Invalid request. Calendar Not Found.'
  
  onShow: ->
    @options.onShow @
  
  redraw: ->
    view = @options.view
    @reset()
    @draw()
    @controls.find('.view .' + view).addClass 'selected'
    @updateControls @getDate('Y-m-d'), view
  
  drawDay: ->
    @element.addClass 'view-day'
    @controls.find('.view .day').addClass 'selected'      
    @table = $('<table>').appendTo @element
    @drawDaysUI 1

  drawWeek: ->
    @element.addClass 'view-week'
    @controls.find('.view .week').addClass 'selected'
    @table = $('<table>').appendTo @element
    @drawDaysUI 7

  drawDaysUI: (numDays)->
    thead = $('<thead>').appendTo @table
    tr    = $('<tr>').appendTo thead
    tr.append '<th width="9%">'
    
    if numDays>1
      offsetDays = @getDate()._date.getDay() - @options.weekStart
      offsetDays = if offsetDays<0 then numDays+offsetDays else offsetDays
      startDate = @getDate()._date
      startDate = new Date(startDate.getTime() - offsetDays*DAY_MS)
      perc = 91/numDays
      for i in [0..numDays]
        k=i+@options.weekStart
        if k>(numDays-1)
          k-=numDays
        
        t = startDate.getTime() + i * DAY_MS
        date = new Date(t)
        tr.append '<th width="'+perc+'%">'+@options.dayNames.long[k]+', '+date.getDate()+'</th>'
    else
      startDate = @getDate()._date
      k = startDate.getDay()
      tr.append '<th width="91%">'+@options.dayNames.long[k]+', '+startDate.getDate()+'</th>'
    
    # draw Rows
    tbody = $('<tbody>').appendTo @table
    timeStep = @options.timeStep
    for i in [0..(60/timeStep*24)]
      tr = $('<tr>').appendTo tbody
      timeStart = @getDate().min2time i*timeStep
      timeEnd   = @getDate().min2time (i+1)*timeStep
      
      th = $ '<th class="label">'+timeStart+" - "+timeEnd+'</th>'
      tr.append th
      if timeStart.match(/^\d+:00/)
        th.addClass 'hour-start'
      
      start = new MyDate(startDate.getFullYear()+'-'+(startDate.getMonth()+1)+'-'+startDate.getDate())
      
      tmp = timeStart.match(/(\d+)/g)
      start.setHours parseInt(tmp[0], 10)
      start.setMinutes parseInt(tmp[1], 10)
      
      for j in [0..numDays]
        d = start.addTime(j*DAY_MS);
        td = $('<td>&nbsp;</td>');
        td.attr('date', d.format('Y-m-dTH-i'));
        
        if numDays>1 && d.format('Y-m-d') == @getDate('Y-m-d')
          td.addClass 'current'

        tr.append td
        
        @drawItemsFor d.format('Y-m-dTH-i')

  drawMonth: ->
    @element.addClass 'view-month'
    @controls.find('.view .month').addClass 'selected'
    
    @table = $('<table>').appendTo @element
    thead = $('<thead>').appendTo @table
    
    # draw Header
    tr = $('<tr>').appendTo thead
    for i in [0..7]
      k = i + @options.weekStart
      if k>6
        k-=7
      tr.append '<th width="14%">'+@options.dayNames.long[k]+'</th>'
    
    # draw Rows
    tbody = $('<tbody>').appendTo @table
    timeStep = @options.timeStep
    
    startDate = new Date(@getDate('Y'),@getDate('m')-1,1)
    offset = startDate.getDay() - @options.weekStart
    offset = if offset<0 then 7+offset else offset
    startDate = new Date(startDate.getTime() - offset*DAY_MS)
    passed=0
    theMonth = parseInt(@getDate('m'), 10)
    for i in [0..6]
      k = 0
      tr = $('<tr>').appendTo tbody
      for j in [0..7]
        passed = k * DAY_MS
        date = new Date(startDate.getTime() + passed)
        mdate = new MyDate(date.getFullYear()+'-'+(date.getMonth()+1)+'-'+date.getDate())

        td = $('<td>')
        td.attr 'date', mdate.format('Y-m-d')
        if theMonth != date.getMonth()+1
          td.addClass 'other-month'
        if @getDate('Y-m-d') == mdate.format('Y-m-d')
          td.addClass 'current'
        td.append '<span class="date-label">'+date.getDate()+'</span>'
        tr.append td
        
        @drawItemsFor mdate.format('Y-m-d')
        k++
    
  drawControls: ->
    @controls = $('<div class="controls">').appendTo @element
    
    prev = $ '<a class="btn prev">'
    prev
      .data 'calendar', @
      .appendTo @controls
      .html @options.prevText
      .attr 'href', @getURLParamsPrev()
      .click ->
        $(@).data 'calendar'
            .load @href
        false
    
    next = $ '<a class="btn next">'
    next
      .data 'calendar', @
      .appendTo @controls
      .html @options.nextText
      .attr 'href', @getURLParamsNext()
      .click ->
        $(@).data('calendar')
            .load(@href);
        false
    
    ym = $('<div class="change-date">').appendTo @controls
    selMonth = $('<select class="month">').appendTo ym
    for i in [0..@options.monthNames.long.length]
      m   = @options.monthNames.long
      sel = if i+1==@getDate('m') then ' selected="selected"' else ''
      selMonth.append '<option value="'+(i+1)+'"'+sel+'>'+m[i]+'</option>'
    selMonth
      .data 'calendar', @
      .change ->
        cal = $(@).data 'calendar'
        y = cal.getDate 'Y'
        d = cal.getDate 'd'
        cal.setDate y+'-'+@value+'-'+d
        location.href = cal.getURLParams cal.getDate('Y-m-d')
        cal.redraw()
      
      selYear = $('<select class="year">').appendTo(ym);
      start = @options.years[0]
      if start.toString().match(/^[-+]/)
        start = parseInt(@getDate('Y'), 10) + parseInt(start, 10)
      
      end = @options.years[1]
      if end.toString().match(/^[-+]/)
        end = parseInt(@getDate('Y'), 10) + parseInt(end, 10)

      end=start if end<start
      
      for i in[start..end]
        sel = if i==@getDate('Y') then ' selected="selected"' else ''
        selYear.append '<option value="'+i+'"'+sel+'>'+i+'</option>'
      
      selYear
        .data 'calendar', @
        .change ->
          cal = $(@).data 'calendar'
          m = cal.getDate 'm'
          d = cal.getDate 'd'
          cal.setDate @value+'-'+m+'-'+d
          location.href = cal.getURLParams cal.getDate('Y-m-d')
          cal.redraw()
      
      view = $('<div class="view">').appendTo(@controls)
      month = $('<a class="btn month">')
      month
        .attr 'href', @getURLParams(null, 'month')
        .appendTo(view)
        .html(@options.textMonth)
        .data('calendar', @)
        .click ->
          cal = $(@).data('calendar')
          cal.options.view = 'month'
          cal.redraw()
      
      week = $('<a class="btn week">')
      week
        .attr('href', @getURLParams(null, 'week'))
        .appendTo(view)
        .html(@options.textWeek)
        .data('calendar', @)
        .click ->
          cal = $(@).data('calendar')
          cal.options.view = 'week'
          cal.redraw()
      
      day = $('<a class="btn day">')
      day
        .attr('href', @getURLParams(null, 'day'))
        .appendTo(view)
        .html(@options.textDay)
        .data('calendar', @)
        .click ->
          cal = $(@).data('calendar')
          cal.options.view = 'day'
          cal.redraw()
      
      view.find('.btn.' + @options.view).addClass('selected')
  
  drawAllItems: ->
    @table.find('.calendar-item').remove()
    
    switch @options.view
      when 'month'
        for x in @itemsByDate
          @drawItemsFor(x)
      else
        for x of @itemsByTime
          @drawItemsFor x
  
  redrawItems: (index)->
    @table.find('.calendar-item').remove()
    @drawItemsFor index
  
  drawItemsFor: (index)->
    switch @options.view
      when 'month'
        items = @itemsByDate[index]
        if items && items.length
          td  = @table.find('td[date='+index+']')
          c   = td.children('.calendar-items-container').eq(0)
          unless c.length
            c = $('<div class="calendar-items-container">').appendTo(td)
          
          for i in [0..items.length]
            item = items[i]
            el = @createItemHTML(item)
            c.append(el)
      
      else
        items = if index then @itemsByTime[index] else @items
        if items && items.length
          td = @table.find('td[date='+index+']')
          c   = td.children('.calendar-items-container').eq(0)
          unless c.length
            c = $('<div class="calendar-items-container">').appendTo(td)
          
          for i in [0..items.length]
            item = items[i]
            el = @createItemHTML(item)
            el.css
              top:   20*i,
              width: td.width() - 30
            c.append(el)

  createItemHTML: (item)->
    el = $('<span class="calendar-item">');
    el
        .attr('id', 'item-'+item.id)
        .data('itemId', item.id)
        .data('item', item)
        .data('calendar', this)
        .html(if item.title then item.title else '&nbsp;')
    
    rem = $('<a href="javascript:void(0)" class="remove">x</a>')
    rem
      .data('calendar', this)
      .click ->
        cal = $(this).data('calendar')
        cal.removeItem($(this).parent().data('itemId'))
        false
    el.append(rem)
    
    el.click ->
      cal = el.data('calendar')
      item = el.data('item')
      options.itemClick(item, cal)
    
    el.draggable
      revert: 'invalid'
      stack: '.calendar-container .calendar-item'
      helper: 'clone'
    
    el
  
  updateControls: (date, view)->
    d = @getDate()
    d = new MyDate(date) if date
    
    v = @options.view
    v = biew if view
    
    # prev,next
    @controls.find('.btn.next').attr('href', @getURLParamsNext(d.format('Y-m-d'),v))
    @controls.find('.btn.prev').attr('href', @getURLParamsPrev(d.format('Y-m-d'),v))
    
    # select month
    @controls.find('.change-date option').removeAttr('selected')
    index = @controls
      .find('.change-date .month option[value='+(d._date.getMonth()+1)+']')
      .index()
    @controls.find('.change-date .month')[0].selectedIndex = index
    
    # select year
    index = @controls
      .find('.change-date .year option[value='+d._date.getFullYear()+']')
      .index()
    @controls.find('.change-date .year')[0].selectedIndex = index
    
    # view month
    params = @getURLParams(d.format('Y-m-d'), 'month')
    @controls.find('.view .month').attr('href', params)
    
    # view week
    params = @getURLParams(d.format('Y-m-d'), 'week')
    @controls.find('.view .week').attr('href', params)
    
    # view day
    params = @getURLParams(d.format('Y-m-d'), 'day')
    @controls.find('.view .day').attr('href', params)
  
  reset: ->
    @element
        .removeClass('view-month')
        .removeClass('view-week')
        .removeClass('view-day')
    @table.remove()
    @controls.find('.view .btn').removeClass('selected')

Calendar.get = (uuid)->
  __Calendars[uuid]
Calendar.set = (c)->
  __Calendars[c.uuid] = c

###
 # Custom Date Object
 #
 # @param  String  date
 #         actual date value in format:
 #         yyyy-mm-dd or yyyy-mm-dd hh:mm:ss 
 ###
class MyDate
  _date: new Date()

  constructor: (date)->
    if date
      d = date.match(/(\d+)/g)
      for i in [0..d.length]
        d[i] = parseInt(d[i], 10)
      
      h=0
      m=0
      s=0
      if d.length>3
        h = parseInt(d[3], 10); h = if h>0 then (if h>23 then 23 else h) else 0
        m = parseInt(d[4], 10); m = if m>0 then (if m>59 then 59 else m) else 0
        s = parseInt(d[5], 10); s = if s>0 then (if s>59 then 59 else s) else 0
        @_date = new Date(d[0], parseInt(d[1], 10)-1, d[2], h, m, s)
      else
        @_date = new Date(d[0], parseInt(d[1], 10)-1, d[2])
    
  format: (format)->
      ret = ""; val=""
      items = format.split(/(.{1})/g)

      for i in [0..items.length]
        val = items[i]
        switch items[i]
            when "Y"
              val = @_date.getFullYear()
            when "m"
              val = @_date.getMonth() + 1
              val = if val>9 then val else '0'+val
            when "M"
              val = @_date.getMonth()
              val = MONTH_NAMES.long[val]
            when "d"
              val = @_date.getDate()
              val = if val>9 then val else '0'+val
            when "D"
              val = @_date.getDay()
              val = DAY_NAMES.long[val]
            when "H"
              val = @_date.getHours();
              val = if val>9 then val else '0'+val
            when "i"
              val = @_date.getMinutes();
              val = if val>9 then val else '0'+val
            when "s"
              val = @_date.getSeconds()
              val = if val>9 then val else '0'+val
            else
              val = ""
        
        ret += val
      return ret

  setHours: (h)->
    @_date.setHours(h)
  setMinutes: (m)->
    @_date.setMinutes(m)
  
  getDate: ->
    parseInt(@format('d'), 10)
  getDay: ->
    @format('D')
  getWeekDay: ->
    @_date.getDay()
  min2time: (minutes)->
    time = minutes;
    h = Math.floor(minutes/60)
    h = if h>9 then (if h>23 then '00' else h) else '0'+h
    
    m = minutes%60
    m = if m>9 then m else '0'+m
    
    h+":"+m

  addTime: (ms)->
    d = new Date(@_date.getTime() + ms)
    str = d.getFullYear()+'-'+(d.getMonth()+1)+'-'+d.getDate()
    str += " " + d.getHours() + ':' + d.getMinutes() + ':' + d.getSeconds()
    new MyDate(str)
  
  addMonth: (add)->
    year  = @_date.getFullYear()
    month = @_date.getMonth()
    
    month += add
    if month>11
      month -= 12
      year++
    else if month<0
      month += 12
      year--
    d = new Date(year, month, @_date.getDate())
    new MyDate(d.getFullYear()+'-'+(d.getMonth()+1)+'-'+d.getDate())